\chapter{系统设计}
\section{系统技术选型及总体设计}
\subsection{开发技术与开发环境选型}
前端采用Vue.js框架，它是一个逐渐构建的库，非常适合开发单页应用(SPA)。Vue专注于视图层，易于掌握，易于与其他库或其他项目整合。Arco Design UI是一个构建在Vue之上的组件库，能够快速地搭建前端界面，对前端进行界面设计以减少开发量。前端采用Axios库与服务器进行通信，Axios 是一个基于promise网络请求库，可以实现同一套代码可以运行在浏览器和Node.js中。\par
因该评测系统采用动态评价方式，为避免出现用户提交恶意代码的情况，系统采用Docker容器对用户代码进行沙箱评测。Docker是一个开源容器引擎，可以通过打包应用程序和依赖并将它们组装为一个“镜像”，在隔离的进程、文件系统、网络和设备环境中进行安全的代码运行。\par
系统的运作对环境有特定的需求，开发环境如表3.1所示。\par

\begin{table}[!htbp]
  \caption{开发环境}
  \centering
  \setlength{\tabcolsep}{5pt}% column separation
  \renewcommand{\arraystretch}{1.5}%row space 
  \begin{tabular*}{35cc}{@{\extracolsep{\fill}}ccc}
    \toprule[2pt]
    环境 & 项目 & 选型\\
    \midrule
    & 操作系统 & Windows 10\\
    & API调试工具 & Apifox\\
    本地 & 集成开发环境 & IntelliJ IDEA\\
    & 其他工具 & Docker Desktop\\
    & 浏览器 & Chrome\\
    \midrule
    & 操作系统 & Ubuntu 22.04\\
    & 评测机容器 & Docker 20.10.7\\
    服务器 & 数据库 & MySQL 5.7、Redis 6.0\\
    & 微服务框架 & Spring Cloud Alibaba 2021.0.0\\
    & API网关 & Spring Cloud Gateway 2.2.4\\
    \bottomrule[2pt]
  \end{tabular*}
\end{table}
系统由前端、服务端、代码沙箱端。前端作为人机交互界面，用户通过前端进行所有操作。服务端负责与前端和代码沙箱端的数据交互。代码沙箱端用来执行代码测试，并把运行结果返回给服务端，是整个系统的核心。代码沙箱端使用 Docker技术\citep{bib15}构建容器，使用工厂模式为代码分配沙箱，来自服务端的目标文件经过代码沙箱的运行后，代码沙箱将运行结果返回给服务端。\par
在线代码评测系统采用微服务架构\citep{bib16}，软件层次分为应用层、网关层、服务层和数据层，通过分层的方式提高系统的可维护性，并对大型项目开发提高开发效率。系统软件层次结构如图3.1所示。\par
\begin{figure}[H]
  \centering
  \includegraphics[width=0.8\textwidth]{Img/img3.2}
  \caption{系统软件层次架构设计图}
\end{figure}
\subsection{系统可行性分析}
从经济层面上讲，各服务在同一台服务器部署，内存至少占用8G，同时磁盘空间需能存储所有用户的操作，至少需要128G。由于支持分布式部署，随着机器的增加，单机的配置可减少。\par
由于本项目使用一般的云服务器即可满足使用需求，所以本项目的经济投入较省。\par
从技术层面上讲，整个在线代码评测系统可分为前端、服务端和代码沙箱端。前端面向用户，以友好的格式将数据呈现在用户眼前，并提供易用的操作界面供用户使用，服务端负责执行业务逻辑并处理数据的流转，代码沙箱端只负责在Linux沙箱中运行编译好的源文件，并把结果返回给服务端。前端与服务器端以及服务器端与代码沙箱端之间通过HTTP协议进行信息交换，接口的URL和数据结构遵循 RESTful风格以便于后续维护。前端使用Vue.js和Arco Design UI构建\citep{bib17}，网络请求使用Axios库。服务器端基于微服务框架完成的，微服务框架采用了Spring Cloud Alibaba实现，数据层基于MySQL数据库。代码沙箱端使用Docker容器技术对用户编写的代码进行实时评测，所谓实时评测是指在获取用户代码后，系统会将其编译为可执行文件，然后运行该文件，并将其传送至代码沙箱进行测试数据的输入和评测。代码沙箱对用户代码采用动态评测的方式，动态评测是一种黑箱评测方法，评测机只关心程序运行结果是否与答案一致，而不关心具体实现细节，对参与评测代码的要求比较灵活。\par
由于本项目对技术框架的选定进行了斟酌，使用了成熟稳定的技术框架，且在类似项目上这套框架可以正常运行，所以本项目在技术选型上有一定的可行性。\par

\section{系统微服务及功能模块设计}
该系统采用Spring Cloud作为微服务架构的框架，微服务实例都是基于Spring Boot的应用，系统的微服务架构设计如图3.2所示。\par
\begin{figure}[H]
  \centering
  \includegraphics[width=0.8\textwidth]{Img/img3.3}
  \caption{微服务架构设计图}
\end{figure}
Feign模块在多个服务之间承担通信的职责，当集群服务部署了多个实例时，它会依据负载均衡的策略挑选一个实例来进行通信。\par
Sentinel负责服务熔断降级流量管理。Sentinel服务模块利用服务熔断和降级流量控制避免短时间内无法处理大量请求而导致实例出现问题，进而影响到其他服务的正常运行。\par
\subsection{用户管理功能模块设计}
用户管理功能模块主要负责用户的注册、登录、修改信息和退出登录等功能。用户注册时需要填写账号、密码、昵称等信息，注册成功后会生成一个唯一的用户ID。用户登录时需要输入账号和密码，系统会验证账号和密码是否正确，登录成功后会生成一个token，token用于后续的身份验证。用户可以修改个人信息，包括昵称和简介等。退出登录时，系统会清除token，用户需要重新登录才能访问系统。\par
\subsection{代码评测功能模块设计}
本项目采用动态评价的代码评测方法，动态评价的代码评测方法要求服务器将编译成的二进制代码文件传递给代码沙箱，后端把编译好的二进制文件交给代码沙箱，代码沙箱通过启动Docker容器执行二进制代码文件，并把输入数据以标准输入的形式一并交给代码沙箱，运行结束后代码沙箱把运行后的输出信息（包括错误流）返回给后端。\par
\subsection{代码管理功能模块设计}
代码管理功能模块负责对代码进行管理，包括存放用户提交的代码及评测结果。学生可查看代码的评测记录，可以查看历史代码。\par
\subsection{题库管理功能模块设计}
用于对题库中的题目进行统一管理。学生能够查看题目、进行代码编写和提交。教师可以对题目进行增加、删除，删除题目后，无法在题库中看到此题目，不过此题目的提交记录依然存在；教师还可以更改题目详情，包括题目标题、题目描述、时间空间限制和评测用例等，题目描述使用Markdown语法。\par
\section{系统数据库设计}
在系统数据库的设计中，需要设计E-R图和数据库表。\par
\subsection{E-R图设计}
这个系统主要包含4个实体，包括学生实体、教师实体、题目实体和提交实体等。\par
\begin{enumerate}
  \item 学生实体的E-R图如图3.3所示。\par
  \begin{figure}[H]
    \centering
    \includegraphics[width=0.8\textwidth]{Img/img3.12}
    \caption{学生实体E-R图}
  \end{figure}
  \item 教师实体的E-R图如图3.4所示。\par
  \begin{figure}[H]
    \centering
    \includegraphics[width=0.8\textwidth]{Img/img3.11}
    \caption{教师实体E-R图}
  \end{figure}
  \item 题目实体的E-R图如图3.5所示。\par
  \begin{figure}[H]
    \centering
    \includegraphics[width=0.8\textwidth]{Img/img3.23}
    \caption{题目实体E-R图}
  \end{figure}
  \item 提交实体的E-R图如图3.6所示。其中运行结果保存了代码最终的运行状态，比如是否通过、是否有编译错误等。运行信息则显示了在此次评测中，程序运行所消耗的时间和内存，以及通过的测试用例数量。\par
  \begin{figure}[H]
    \centering
    \includegraphics[width=0.8\textwidth]{Img/img3.24}
    \caption{提交实体E-R图}
  \end{figure}
\end{enumerate}
各个实体属性的E-R图如上，而实体间关系的E-R图如图3.7。\par
\begin{figure}[H]
  \centering
  \includegraphics[width=0.8\textwidth]{Img/img3.21}
  \caption{代码在线评测系统数据库E-R图}
\end{figure}
\subsection{数据库表设计}
在创建数据库表时，要对表的字段名、字段类型、约束、字段描述信息等进行定义。字段名、字段类型要在实际建表时确定。约束用来规定这个字段是否自增、是否是主键、是否允许为NULL值以及是否有默认值。\par
学生表负责存储学生信息，见表3.2。\par
\begin{table}[!htbp]
  \caption{学生表 tb\_student}
  \centering
  \setlength{\tabcolsep}{5pt}% column separation
  \renewcommand{\arraystretch}{1.5}%row space 
  \begin{tabular*}{35cc}{@{\extracolsep{\fill}}cccc}
    \toprule[2pt]
    字段名 & 字段类型 & 约束 & 说明\\
    %\cline{2-9}% partial hline from column i to column j
    \midrule
    userId        & bigint    & 自增，主键，非空               & 用户id\\
    userAccount   & varchar   & 非空                          & 账号\\
    userPassword  & varchar   & 非空                          & 密码\\
    userName      & varchar   &                               & 用户昵称\\
    userAvatar    & varchar   &                               & 用户头像\\
    userProfile   & text      &                               & 用户简介\\
    createTime    & datetime  & 非空，默认值CURRENT\_TIMESTAMP & 创建时间\\
    updateTime    & datetime  & 非空，默认值CURRENT\_TIMESTAMP & 更新时间\\
    isDelete      & tinyint   & 非空，默认值0                  & 是否删除\\
    \bottomrule[2pt]
  \end{tabular*}
\end{table}

教师表负责存储教师信息，见表3.3。\par

\begin{table}[!htbp]
  \caption{教师表 tb\_teacher}
  \centering
  \setlength{\tabcolsep}{5pt}% column separation
  \renewcommand{\arraystretch}{1.5}%row space
  \begin{tabular*}{35cc}{@{\extracolsep{\fill}}cccc}
    \toprule[2pt]
    字段名 & 字段类型 & 约束 & 说明\\
    %\cline{2-9}% partial hline from column i to column j
    \midrule
    userId        & bigint    & 自增，主键，非空              & 用户id\\
    userAccount   & varchar   & 非空                         & 账号\\
    userPassword  & varchar   & 非空                         & 密码\\
    userName      & varchar   &                              & 用户昵称\\
    userAvatar    & varchar   &                              & 用户头像\\
    userProfile   & text      &                              & 用户简介\\
    createTime    & datetime  & 非空，默认值CURRENT\_TIMESTAMP & 创建时间\\
    updateTime    & datetime  & 非空，默认值CURRENT\_TIMESTAMP & 更新时间\\
    isDelete      & tinyint   & 非空，默认值0                  & 是否删除\\
    \bottomrule[2pt]
  \end{tabular*}
\end{table}

题目表负责存储题目信息，见表3.4。其中“标签列表”字段为JSON格式的字符串数组，“测试用例”字段为JSON格式的对象数组，时间和空间限制则表示为JSON格式的对象。时间限制的单位是毫秒（ms），默认值为1000；内存限制的单位是兆字节（MB），默认值为64。\par

\begin{table}[!htbp]
  \caption{题目表 tb\_question}
  \centering
  \setlength{\tabcolsep}{5pt}% column separation
  \renewcommand{\arraystretch}{1.5}%row space 
  \begin{tabular*}{35cc}{@{\extracolsep{\fill}}cccc}
    \toprule[2pt]
    字段名 & 字段类型 & 约束 & 说明\\
    %\cline{2-9}% partial hline from column i to column j
    \midrule
    questionId  & bigint   & 自增，主键，非空               & 题目id\\
    userId      & bigint   & 非空                          & 创建教师userId\\
    title       & varchar  &                               & 题目标题\\
    content     & text     &                               & 题目题面\\
    tags        & varchar  &                               & 标签列表\\
    judgeCase   & text     &                               & 测试用例\\
    judgeConfig & text     &                               & 时间空间限制\\
    submitNum   & int      & 非空，默认值0                  & 提交数\\
    acceptNum   & int      & 非空，默认值0                  & 通过数\\
    createTime  & datetime & 非空，默认值CURRENT\_TIMESTAMP & 创建时间\\
    updateTime  & datetime & 非空，默认值CURRENT\_TIMESTAMP & 更新时间\\
    isDelete    & tinyint  & 非空，默认值0                  & 是否删除\\
    \bottomrule[2pt]
  \end{tabular*}
\end{table}

提交表存储用户每次代码提交的数据，见表3.5。提交表的每条提交记录对应用户的一次提交代码操作，在不同的题目下提交，提交记录存储当前题目对应的题目id。“编程语言”字段标识了用户代码使用的编程语言。“运行信息”是JSON对象，存储了运行时间、通过样例数目及 error\_info、wrong\_data等信息。\par\par

\begin{table}[htb]
  \caption{提交表 tb\_run}
  \centering
  \setlength{\tabcolsep}{5pt}% column separation
  \renewcommand{\arraystretch}{1.5}%row space 
  \begin{tabular*}{35cc}{@{\extracolsep{\fill}}cccc}
    \toprule[2pt]
    字段名 & 字段类型 & 约束 & 说明\\
    %\cline{2-9}% partial hline from column i to column j
    \midrule
    runId       & bigint   & 自增，主键，非空               & 提交id\\
    questionId  & bigint   & 作为索引                      & 提交题目id\\
    userId      & bigint   & 作为索引                      & 创建学生userId\\
    language    & varchar  &                               & 编程语言\\
    code        & text     &                               & 源代码\\
    result      & tinyint  &                               & 运行结果\\
    otherInfo   & text     &                               & 运行信息\\
    submitTime  & datetime & 非空，默认值CURRENT\_TIMESTAMP & 创建时间\\
    isDelete    & tinyint  & 非空，默认值0                  & 是否删除\\
    \bottomrule[2pt]
  \end{tabular*}
\end{table}

其中“运行结果”字段值的含义见表3.6所示。\par
\begin{table}[!htbp]
  \caption{result字段值含义}
  \centering
  \setlength{\tabcolsep}{5pt}% column separation
  \renewcommand{\arraystretch}{1.5}%row space 
  \begin{tabular*}{35cc}{@{\extracolsep{\fill}}cccc}
    \toprule[2pt]
    result值 & 含义\\
    %\cline{2-9}% partial hline from column i to column j
    \midrule
    4 & 通过（accepted，AC）\\
    6 & 输出错误（wrong answer，WA）\\
    7 & 超时（time limit exceeded，TLE）\\
    8 & 内存超限（memory limit exceeded，MLE）\\
    9 & 输出错误（output limit exceeded，OLE）\\
    10 & 运行错误（runtime error，RE）\\
    11 & 编译错误（compilation error，CE）\\
    14 & 系统错误（system error，SE）\\
    \bottomrule[2pt]
  \end{tabular*}
\end{table}
